#include "Client.h"
#include "NetworkMessenger.h"
#include "../Utility/Misc/DebugOutput.h"
#include "../EventSystem/EventSystem.h"

#include <string>
#include <fstream>
using std::fstream;
using std::string;
using std::ios_base;
using std::istringstream;

Socket								Client::clientSocket;
Address								Client::serverAddress;
NetworkMessage						Client::clientMessage;
NetworkHeader						Client::clientHeader;
unsigned char						Client::sequenceNumber;
vector<Client::MessageParserFunc>	Client::msgParser;
Timer								Client::timer;
unsigned int						Client::sequence;
float								Client::msgTimer;
float								Client::msgRate;
float								Client::broadcastWait;
bool								Client::isRunning;

void Client::Init()
{
	LoadClientSettings("Res/Data/Networking/clientData.txt");

	isRunning = true;
	msgParser.reserve(CL_MAX);
	msgParser.push_back(ParseConnectMessage);
	msgParser.push_back(ParseFullMessage);
	msgParser.push_back(ParseShutdownMessage);
	msgParser.push_back(ParsePlayerMoveMessage);

	EventSystem::RegisterClient(PLAYER_MOVE_CL, HandleEvent);

	InitializeSockets();
	//GetIPAddress("Res/Data/Networking/serverIP.txt");
	clientSocket.OpenClient(serverAddress);

	clientSocket.SetBroadcast();
	SendBroadcastRequest();

	timer.Init();
	msgTimer = 0.0f;

	sequence = 255;

	isRunning = ServerBroadcastResponse(broadcastWait);

	WriteMessageHeader(clientMessage, clientHeader);
}

void Client::Update()
{
	timer.TimeStep();

	NetworkMessage incomingMessage;

	int bytesRead = clientSocket.Receive(serverAddress, incomingMessage.GetData(), incomingMessage.GetMaxBufferSize());
	incomingMessage.SetBufferSize(bytesRead);

	if( bytesRead )
	{
		if(MSG_UNRELIABLE == incomingMessage.ReadByte())
		{

#ifdef _DEBUG
			std::ostringstream msg;
			msg << "Client Recieved <" << bytesRead << "> bytes from: " << (int)serverAddress.GetA() << "." << (int)serverAddress.GetB() << "." << (int)serverAddress.GetC() << "." << (int)serverAddress.GetD();
			DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif
			unsigned char newSequence = incomingMessage.ReadByte();
			if(IsSequenceMoreRecent(newSequence, sequence))
			{

#ifdef _DEBUG
				std::ostringstream msg;
				msg << "Sequence#:" << sequence;
				DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif
				(*msgParser[incomingMessage.ReadByte()])(incomingMessage, serverAddress);
			}
		}
	}

	msgTimer += timer.GetDeltaTimeFloat();

	if(msgTimer > SNAP_RATE(msgRate))
	{
		msgTimer = 0.0f;

		SendMessages();
	}
}

void Client::SendMessages()
{
	if(clientMessage.GetCurrentBufferSize() > sizeof(NetworkHeader))
	{
#ifdef _DEBUG
		std::ostringstream msg;
		msg << "Sending <" << clientMessage.GetCurrentBufferSize() << "> bytes to: "<< (int)serverAddress.GetA() << "." << (int)serverAddress.GetB() << "." << (int)serverAddress.GetC() << "." << (int)serverAddress.GetD();
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif
		clientSocket.Send(serverAddress, clientMessage.GetData(), clientMessage.GetCurrentBufferSize());

		clientMessage.Clear();
		WriteMessageHeader(clientMessage, clientHeader);
	}
}

void Client::LoadClientSettings(const char* const _filePath)
{
	char line[256];

	fstream in(_filePath, ios_base::in);

	if(in.is_open())
	{
		while( in.getline(line,256) )
		{
			istringstream newLine(line, istringstream::in);

			string command;

			newLine >> command;

			if(command == "BroadcastWait")
			{				
				newLine >> broadcastWait;
			}
			else if(command == "MessageRate")
			{
				newLine >> msgRate;
			}
		}
	}

	in.close();

#ifdef _DEBUG
		std::ostringstream msg;
		msg << "Init client settings\nBroadcast wait time: " << broadcastWait << "ms\nMessage Rate: " << msgRate;
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif

}

void Client::Shutdown()
{
	ShutdownSockets();
}

void Client::GetIPAddress(const char* filename)
{
	std::ifstream in;

	in.open(filename, std::ios_base::in);

	if(!in.is_open())
	{
#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "Failed to retrieve IP address from file");
#endif
		serverAddress.SetAddress(0,0,0,0,0);
		return;
	}

	unsigned int addrA, addrB, addrC, addrD;

	in >> addrA;
	in >> addrB;
	in >> addrC;
	in >> addrD;

	in.close();

	serverAddress.SetAddress(addrA, addrB, addrC, addrD, NET_DEFAULT_PORT);
}

bool Client::SendBroadcastRequest()
{
	WriteMessageHeader(clientMessage, clientHeader);
	ClientMessenger::Broadcast(clientMessage);
	Address broadcastAddr(NET_BROADCAST_IP, NET_DEFAULT_PORT);

#ifdef _DEBUG
	std::ostringstream msg;
	msg << "Broadcasting for servers on port: " << broadcastAddr.GetPort();
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif

	if(!clientSocket.Send(broadcastAddr, clientMessage.GetData(), clientMessage.GetCurrentBufferSize()))
	{
#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "Failed to send broadcasting message");
#endif

		return false;
	}

	clientMessage.Clear();

	return true;
}

bool Client::ServerBroadcastResponse(float _maxWaitTime)
{
	NetworkMessage incomingMessage;

	while(_maxWaitTime > 0.0f)
	{

		int bytesRead = clientSocket.Receive(serverAddress, incomingMessage.GetData(), incomingMessage.GetMaxBufferSize());
		incomingMessage.SetBufferSize(bytesRead);

		if( bytesRead )
		{

			if(MSG_UNRELIABLE == incomingMessage.ReadByte())
			{

#ifdef _DEBUG
				std::ostringstream msg;
				msg << "Client Recieved <" << bytesRead << "> bytes from: " << (int)serverAddress.GetA() << "." << (int)serverAddress.GetB() << "." << (int)serverAddress.GetC() << "." << (int)serverAddress.GetD();
				DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif
				unsigned char newSequence = incomingMessage.ReadByte();
				if(IsSequenceMoreRecent(newSequence, sequence))
				{
					sequence = newSequence;

#ifdef _DEBUG
					std::ostringstream msg;
					msg << "Sequence#:" << sequence;
					DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif
					(*msgParser[incomingMessage.ReadByte()])(incomingMessage, serverAddress);

					return true;
				}
			}
		}

		timer.TimeStep();
		_maxWaitTime -= timer.GetDeltaTimeFloat();
	}

#ifdef _DEBUG
		DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING_FAILURE, "No response from broadcast");
#endif

	return false;
	
}

void Client::ParseConnectMessage(NetworkMessage& _netMsg, Address& _incomingAddress)
{
#ifdef _DEBUG
	std::ostringstream msg;
	msg << "Recieved: " << (int)_incomingAddress.GetA() << "." << (int)_incomingAddress.GetB() << "." << (int)_incomingAddress.GetC() << "." << (int)_incomingAddress.GetD();
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, msg.str().c_str());
#endif
}

// Need to figure out a solution for this
//static PlayerMoveEvent pmEvent(0);
void Client::ParsePlayerMoveMessage(NetworkMessage& _netMsg, Address& _incomingAddress)
{
	PlayerMoveEvent pmEvent(0);
#ifdef _DEBUG
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, "Received player moved message from server");
#endif

	pmEvent.direction = _netMsg.ReadByte();
	EventSystem::SendEvent(PLAYER_MOVE_SV, &pmEvent);
}

void Client::ParseShutdownMessage(NetworkMessage& _netMsg, Address& _incomingAddress)
{
#ifdef _DEBUG
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, "Server Shutdown");
#endif

	isRunning = false;
}

void Client::ParseFullMessage(NetworkMessage& _netMsg, Address& _incomingAddress)
{
#ifdef _DEBUG
	DebugOutput::Print(DebugOutput::NETWORK_MSG, DebugOutput::NETWORKING, "Server Full");
#endif

	isRunning = false;
}

void Client::HandleEvent(Event* _event)
{
	switch(_event->GetEventID())
	{
	case PLAYER_MOVE_CL:
		{
			ClientMessenger::PlayerMove(clientMessage, _event);
		}
		break;
	}
}